﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Tszy.Unlimited.Base.Common;
using Tszy.Unlimited.Base.Extensions;

namespace Tszy.Unlimited.Base.Objects.Property
{
    internal class PropertyAccessor
    {
        public PropertyAccessor(Type type)
        {
            var Properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                        .Where(p => p.CanRead && !p.GetIndexParameters().Any()).AsEnumerable();
            PropertyGetters = Properties.ToDictionary(p => p.Name, p => CreateGetter(p));
            PropertySetters = Properties.Where(p => p.GetSetMethod() != null)
                .ToDictionary(p => p.Name, p => CreateSetter(p));
        }

        private IDictionary<string, Func<object, object>> PropertyGetters { get; set; }

        private IDictionary<string, Action<object, object>> PropertySetters { get; set; }

        public static Func<object, object> CreateGetter(PropertyInfo property)
        {
            if (property == null)
                return null;

            var getter = property.GetGetMethod();
            if (getter == null)
                return null;

            var genericMethod = typeof(PropertyAccessor).GetMethod("CreateGetterGeneric");
            MethodInfo genericHelper = genericMethod.MakeGenericMethod(property.DeclaringType, property.PropertyType);
            return (Func<object, object>)genericHelper.Invoke(null, new object[] { getter });
        }

        public static Func<object, object> CreateGetterGeneric<T, R>(MethodInfo getter) where T : class
        {
            Func<T, R> getterTypedDelegate = (Func<T, R>)Delegate.CreateDelegate(typeof(Func<T, R>), getter);
            Func<object, object> getterDelegate = (Func<object, object>)( (object instance) => getterTypedDelegate((T)instance) );
            return getterDelegate;
        }

        public static Action<object, object> CreateSetter(PropertyInfo property)
        {
            if (property == null)
                return null;

            var setter = property.GetSetMethod();
            if (setter == null)
                return null;

            var genericMethod = typeof(PropertyAccessor).GetMethod("CreateSetterGeneric");
            MethodInfo genericHelper = genericMethod.MakeGenericMethod(property.DeclaringType, property.PropertyType);
            return (Action<object, object>)genericHelper.Invoke(null, new object[] { setter });
        }

        public static Action<object, object> CreateSetterGeneric<T, V>(MethodInfo setter) where T : class
        {
            Action<T, V> setterTypedDelegate = (Action<T, V>)Delegate.CreateDelegate(typeof(Action<T, V>), setter);

            Action<object, object> setterDelegate = (Action<object, object>)( (object instance, object value) =>
             {
                 if (value is V)
                 {
                     setterTypedDelegate((T)instance, (V)value);
                 }
                 else
                 {
                     var type = typeof(V);

                     if (type.BaseType.Name == "Enum")
                     {
                         setterTypedDelegate((T)instance, (V)value);
                     }
                     else
                     {
                         var v1 = Convert.ChangeType(value, typeof(V));

                         if (v1 is DateTime)
                         {
                             var tmpValue = (DateTime)v1;

                             var defaultTime = DateTimeDefault.Value;

                             if (tmpValue < defaultTime)
                             {
                                 v1 = defaultTime;
                             }
                         }

                         setterTypedDelegate((T)instance, (V)v1);
                     }
                 }
             } );
            return setterDelegate;
        }
    }
}